package com.guanghua.brick.web.extend.struts;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts2.config.NullResult;

import com.guanghua.brick.util.BeanUtil;
import com.guanghua.brick.util.FileUtil;
import com.opensymphony.xwork2.ObjectFactory;
import com.opensymphony.xwork2.config.Configuration;
import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.config.ConfigurationProvider;
import com.opensymphony.xwork2.config.entities.ActionConfig;
import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig;
import com.opensymphony.xwork2.config.entities.InterceptorConfig;
import com.opensymphony.xwork2.config.entities.InterceptorMapping;
import com.opensymphony.xwork2.config.entities.PackageConfig;
import com.opensymphony.xwork2.config.entities.ResultConfig;
import com.opensymphony.xwork2.config.entities.ResultTypeConfig;
import com.opensymphony.xwork2.inject.ContainerBuilder;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.interceptor.Interceptor;
import com.opensymphony.xwork2.util.location.LocatableProperties;


/**
 * 集成xwork的config provier，用来加载brick针对struts2的zero config配置
 * @author leeon
 */
public class MethodActionConfigurationProvider implements ConfigurationProvider {
	
	private static final Log logger = LogFactory.getLog(MethodActionConfigurationProvider.class);
	
	/**
	 * 用来获取xwork config对象，通过该对象来加载config
	 */
	private Configuration configuration = null;
	/**
	 * 从外部传入的参数，用来记录哪些package有zero config的配置类，程序好搜索这些pkg
	 */
	private String methodActionPackage = null;
	/**
	 * 用于记录哪些文件被读取过，根据是否修改来判断是否进行reload操作
	 */
	private Map<String, Long> loadedClassUrls = new HashMap<String, Long>();
	/**
	 * xwork的object factory，用来管理result, iterceptor等配置
	 */
	private ObjectFactory objectFactory = null;
	
	private final static String DEFAULT_METHOD_ACTION_PACKAGE = "brick.web.extend.struts.methodActionPackage";
	
	/**
	 * 构造方法
	 * @param methodActionPackage
	 */
	public MethodActionConfigurationProvider() {
	}
	
	/**
	 * 解构方法，清除loaded url
	 */
	public void destroy() {
		loadedClassUrls.clear();
	}
	
	/**
	 * 继承接口的初始化方法，可以获取到config实例
	 */
	public void init(Configuration configuration) throws ConfigurationException {
		this.configuration = configuration;
		this.loadedClassUrls.clear();
	}
	
	/**
	 * 通过xwork inject获取object factory的方法
	 * @param objectFactory
	 */
	@Inject
    public void setObjectFactory(ObjectFactory objectFactory) {
        this.objectFactory = objectFactory;
    }
	
	/**
	 * 通过xwork inject获取methodActionPackage的方法
	 * @param context
	 */
	@Inject(value=DEFAULT_METHOD_ACTION_PACKAGE)
    public void setMethodActionPackage(String methodActionPackage) {
        this.methodActionPackage = methodActionPackage.replace('.', '/');
    }

	/**
	 * 继承接口的load pak的方法
	 */
	public void loadPackages() throws ConfigurationException {
		//读取需要寻找Action类的目录
		String[] l = this.methodActionPackage.split(",");

		//搜索所有package类
		List<String> c = new ArrayList<String>();
		for (String p:l) {
			try {
				c.addAll(FileUtil.searchFileFromClassPath(p, ".*\\.class"));
			} catch (Exception e) {
				logger.warn("search file from method action package error:["+p+"]", e);
			}
		}
		
		//读取package信息
		for (String clsName:c) {
			String className = clsName.substring(0, clsName.length() - 6);
			className = className.replace('/', '.');
			
			Class<?> clazz = null;
			try {
				clazz = Class.forName(className);
			} catch (ClassNotFoundException e) {
				logger.warn("class load method action error:["+className+"]", e);
				continue;
			}
			Package p = (Package)clazz.getAnnotation(Package.class);
			if (p == null) continue;
			//读取package
			PackageConfig pc = createPackageConfig(clazz, p);
			//将该类的路径加入Loadedfile
			try {
				loadedClassUrls.put(clsName, BeanUtil.getClassPathFile(clsName).lastModified());
			} catch (Exception e) {
				logger.warn("get class file last modify date from filesystem error :["+clsName+"]", e);
			}
			logger.trace("find action package and loaded:["+pc.getName()+","+pc.getNamespace()+"]");
			
			//读取action信息
			Method[] m = clazz.getMethods();
			for(Method method:m) {
				Action action = (Action)method.getAnnotation(Action.class);
				if (action == null) continue;
				logger.trace("find action method and start parsing:["+action.name()+","+method.getName()+"]");
				
				//读取action的参数
				Map<String, Object> ps = this.createParameters(action.param());
				
				//读取action的exception mapping信息
				List<ExceptionMappingConfig> le = this.createExceptionMappingConfigs(
						(ActionExceptionMappings)method.getAnnotation(ActionExceptionMappings.class));
				
				//读取单个的e m信息
				ExceptionMappingConfig em = this.createExceptionMappingConfig((ActionExceptionMapping)method.getAnnotation(ActionExceptionMapping.class));
				if (em != null) le.add(em);
		
				//读取action的interceptor信息
				List<InterceptorMapping> li = this.createInterceptorMappings(
						(ActionInterceptors)method.getAnnotation(ActionInterceptors.class), pc);
				
				//读取单个的i信息
				InterceptorMapping im = this.createInterceptorMapping(
						(ActionInterceptor)method.getAnnotation(ActionInterceptor.class), pc);
				if (im != null) li.add(im);
		
				//读取action的result信息
				Map<String, ResultConfig> msr = this.createResultConfigs(
						(ActionResults)method.getAnnotation(ActionResults.class), pc);
				
				//读取单个的action r信息
				ResultConfig rc = this.createResultConfig((ActionResult)method.getAnnotation(ActionResult.class), pc);
				if (rc != null) msr.put(rc.getName(), rc);
				
				ActionConfig ac = new ActionConfig(method.getName(), clazz, ps, msr, li, le);
				pc.addActionConfig(action.name(), ac);
				logger.trace("find action method and end parsing:["+action.name()+","+method.getName()+"]");
			}
			//加载到configuration中
			this.configuration.addPackageConfig(pc.getName(), pc);
		}
	}
	
	/**
	 * 是否允许热加载
	 */
	public boolean needsReload() {
		Set<String> s = loadedClassUrls.keySet();
		for (String clz : s) {
			//获取类的上次修改时间
			long now = 0;
			try {
				now = BeanUtil.getClassPathFile(clz).lastModified();
			} catch (Exception e) {
				logger.warn("get class file last modify date from filesystem error :["+clz+"]", e);
			}
			//根据修改时间和已经记录的修改时间对比，如果发生修改那么reload
			if (loadedClassUrls.get(clz).longValue() != now) {
				logger.trace("find action package class modified and will reload:["+clz+"]");
				return true;
			}
		}
		return false;
	}

	/**
	 * 必须实现但没有作用的方法
	 */
	public void register(ContainerBuilder arg0, LocatableProperties arg1)
			throws ConfigurationException {
	}
	
	/**
	 * 根据参数创建package
	 * @param clazz
	 * @param p
	 * @return
	 */
	private PackageConfig createPackageConfig(Class clazz, Package p) {
		//根据类名创建package
		PackageConfig pc = new PackageConfig(clazz.getName());
		//获取名字空间
		pc.setNamespace(p.namespace());
		//搜索parent,如果没有找到抛出警告
		PackageConfig parent = this.configuration.getPackageConfig(p.parent());
		if (parent != null) pc.addParent(parent);
		else ;
		//返回
		return pc;
	}
	
	
	/**
	 * 装载exception mapping config
	 * 默认使用name和result同名
	 * @param ems
	 * @return
	 */
	private List<ExceptionMappingConfig> createExceptionMappingConfigs(ActionExceptionMappings ems) {
		//构造返回的List
		List<ExceptionMappingConfig> list = new ArrayList<ExceptionMappingConfig>();
		if (ems == null) return list;
		
		//读取aem信息
		ActionExceptionMapping aems[] = ems.value();
		for (ActionExceptionMapping aem : aems) {
			ExceptionMappingConfig emc = new ExceptionMappingConfig(
					aem.result(), aem.exceptionClass().getName(), aem.result(), createParameters(aem.param()));
			if (emc != null) list.add(emc);
		}
		//返回结果list
		return list;
	}
	
	/**
	 * 用于构造单个的action exception mapping
	 * @param em
	 * @return
	 */
	private ExceptionMappingConfig createExceptionMappingConfig(ActionExceptionMapping em) {
		if (em == null) return null;
		ExceptionMappingConfig emc = new ExceptionMappingConfig(
				em.result(), em.exceptionClass().getName(), em.result(), createParameters(em.param()));
		return emc;
	}
	
	
	/**
	 * 装载interceptor 集合
	 * @param is
	 * @param pc
	 * @return
	 */
	private List<InterceptorMapping> createInterceptorMappings(ActionInterceptors is, PackageConfig pc) {
		//构造返回的i集合列表
		List<InterceptorMapping> list = new ArrayList<InterceptorMapping>();
		if (is == null) return list;
		//调用单个的构造方法进行构造
		ActionInterceptor ais[] = is.value();
		for (ActionInterceptor ai : ais) {
			InterceptorMapping im = createInterceptorMapping(ai, pc);
			if (im != null) list.add(im);
		}
		//返回结果
		return list;
	}
	
	/**
	 * 装载单个的interceptor
	 * @param interceptor
	 * @param pc
	 * @return
	 */
	private InterceptorMapping createInterceptorMapping(ActionInterceptor interceptor, PackageConfig pc) {
		if (interceptor == null) return null;
		//i的参数构造
		Map param = createParameters(interceptor.param());
    	//i的名称
		String name = interceptor.value();
    	//通过i的名字去所有的i中查询 i config
		Object o = pc.getAllInterceptorConfigs().get(name);
    	if (o instanceof InterceptorConfig) {
    		InterceptorConfig config = (InterceptorConfig) o;
            //通过config去加载 真正的 i
    		Interceptor inter = null;
            try {
                inter = objectFactory.buildInterceptor(config, param);
                return new InterceptorMapping(name, inter);
            } catch (ConfigurationException ex) {
                logger.warn("Unable to load config class "+config.getClassName()+" at "+
                        ex.getLocation()+" probably due to a missing jar, which might "+
                        "be fine if you never plan to use the "+config.getName()+" interceptor", ex);
            }
    	}
		return null;
	}
	
	
	
	/**
	 * 创建result 集合
	 * @param rs
	 * @param pc
	 * @return
	 */
	private Map<String, ResultConfig> createResultConfigs(ActionResults rs, PackageConfig pc) {
		Map<String, ResultConfig> map = new HashMap<String, ResultConfig>();
		if (rs == null) return map;
		
		ActionResult ars[] = rs.value();
		for (ActionResult ar : ars) {
			ResultConfig rc = createResultConfig(ar, pc);
			if (rc != null) map.put(rc.getName(), rc);
		}
		return map;
	}
	
	 /**
     * Creates a default ResultConfig,
     * using either the resultClass or the default ResultType for configuration package
     * associated this ResultMap class.
     *
     * @param key The result type name
     * @param resultClass The class for the result type
     * @param location Path to the resource represented by this type
     * @return A ResultConfig for key mapped to location
     */
    @SuppressWarnings("unchecked")
	private ResultConfig createResultConfig(ActionResult result, PackageConfig pc) {
    	if (result == null) return null;
    	
    	Map param = createParameters(result.param());
    	Class clazz = result.type();
    	
    	//判断是否使用默认的result type,是的话需要去取默认result type的实现类
    	if (clazz == NullResult.class) {
			String defaultResultType = pc.getFullDefaultResultType();
			ResultTypeConfig resultType = pc.getAllResultTypeConfigs().get(defaultResultType);
			if (resultType.getParams() != null) param.putAll(resultType.getParams());
			try {
				clazz = Class.forName(resultType.getClazz());
			} catch (ClassNotFoundException ex) {
			}
		}

		//设定default param也就是location
    	String defaultParam;
		try {
			defaultParam = (String) clazz.getField("DEFAULT_PARAM").get(null);
		} catch (Exception e) {
			// not sure why this happened, but let's just use a sensible choice
			defaultParam = "location";
		}
		param.put(defaultParam, result.value());
		
		//创建result config返回
		return new ResultConfig(result.name(), clazz.getName(), param);
	}
	
	/**
	 * 构建param anno参数的map的方法
	 * @param ps
	 * @return
	 */
	private Map<String, Object> createParameters(Param[] ps) {
		Map<String, Object> map = new HashMap<String, Object>();
		if (ps == null) return map;
		for (Param p: ps) {
			map.put(p.name(), p.value());
		}
		return map;
	}
	
}
